package utils

import (
	"log"
	"reflect"
	"strings"

	"github.com/pkg/errors"
)

const (
	ErrorTypeError = "error input interface type error"
	TagNoExisted   = "error remap tag no existed"
)

func reflectMakeNew(t reflect.Type) interface{} {
	retptr := reflect.New(t)
	sval := retptr.Elem().Interface()
	return sval
}

type TagMap map[string]string

func ReflectTagMap(t reflect.Type) map[string]TagMap {
	// log.Print(t.Kind(), " and ", t.Name())
	if t.Kind() != reflect.Struct {
		return nil
	}
	ret := make(map[string]TagMap)
	num := t.NumField()
	for i := 0; i < num; i++ {
		sub := strings.Split(string(t.Field(i).Tag), "\"")
		for k, v := range sub {
			if len(v) > 0 {
				if k%2 == 0 {
					v = strings.Trim(v, " ")
				}
				if v[len(v)-1] == ':' {
					if _, ok := ret[v[:len(v)-1]]; ok {
						if ((k + 1) < len(sub)-1) && ((sub[k+1][len(sub[k+1])-1]) == ':') {
							continue
						} else {
							ret[v[:len(v)-1]][sub[k+1]] = string(t.Field(i).Name)
						}
					} else {
						ret[v[:len(v)-1]] = make(TagMap)
						log.Print()
						if ((k + 1) < len(sub)-1) && ((sub[k+1][len(sub[k+1])-1]) == ':') {
							continue
						} else {
							ret[v[:len(v)-1]][sub[k+1]] = string(t.Field(i).Name)
						}
					}
				} else {
					continue
				}
			}

		}
	}
	return ret
}

func SameKind(typ1 reflect.Kind, typ2 reflect.Kind) bool {
	switch typ1 {
	case reflect.Int:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	case reflect.Int8:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	case reflect.Int16:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	case reflect.Int32:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	case reflect.Int64:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	case reflect.Uint:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
	case reflect.Uint8:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
	case reflect.Uint16:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
	case reflect.Uint32:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
	case reflect.Uint64:
		if typ2 == reflect.Int || typ2 == reflect.Int16 || typ2 == reflect.Int32 || typ2 == reflect.Int64 || typ2 == reflect.Int8 {
			return true
		}
		if typ2 == reflect.Uint || typ2 == reflect.Uint16 || typ2 == reflect.Uint32 || typ2 == reflect.Uint64 || typ2 == reflect.Uint8 {
			return true
		}
	case reflect.Float32:
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	case reflect.Float64:
		if typ2 == reflect.Float32 || typ2 == reflect.Float64 {
			return true
		}
	default:
		return (typ1 == typ2)
	}
	return false
}

func UnmarshalWithTag(value interface{}, maps map[string]interface{}, tag string) {
	if "" == tag {
		return
	}
	valueof := reflect.ValueOf(value)
	if !valueof.Elem().CanAddr() {
		log.Print("should be addr")
		return
	}
	remap := ReflectTagMap(valueof.Elem().Type())
	_, ok := remap[tag]
	if !ok {
		return
	}
	for k, v := range maps {
		log.Print(k)
		if filedName, ok := remap["json"][k]; ok {
			log.Print(valueof.Elem().FieldByName(filedName).Kind(), reflect.TypeOf(v).Kind().String())
			if SameKind(valueof.Elem().FieldByName(filedName).Kind(), reflect.TypeOf(v).Kind()) {
				if valueof.Elem().FieldByName(filedName).CanSet() {
					valueof.Elem().FieldByName(filedName).Set(reflect.ValueOf(v).Convert(valueof.Elem().FieldByName(filedName).Type()))
				}
			}
		}
	}
}

/*
	an generaly unmarshal function implement
	accept only pointer to struct or pointer to interface contains struct
*/
func UnmarshalJson2Struct(value interface{}, maps map[string]interface{}) error {
	log.Print("UnmarshalJson kind is ", reflect.ValueOf(value).Kind())
	log.Print("UnmarshalJson kind1 is ", reflect.ValueOf(value).Elem().Kind())
	// log.Print("UnmarshalJson kind2 is ", reflect.ValueOf(value).Elem().Elem().Kind())
	valueof := reflect.ValueOf(value)
	var opStruct reflect.Value
	if valueof.Kind() == reflect.Ptr {
		// if it is ptr to interface,the interface must contains a struct type
		if valueof.Elem().Kind() == reflect.Interface {
			if valueof.Elem().Elem().Kind() == reflect.Struct {
				opStruct = valueof.Elem().Elem()
			} else {
				return errors.New(ErrorTypeError)
			}
		} else if valueof.Elem().Kind() == reflect.Struct {
			// simply it is ptr to struct
			opStruct = valueof.Elem()
		} else {
			return errors.New(ErrorTypeError)
		}
	} else {
		return errors.New(ErrorTypeError)
	}

	remap := ReflectTagMap(opStruct.Type())
	_, ok := remap["json"]
	if !ok {
		return errors.New(TagNoExisted)
	}
	for k, v := range maps {
		log.Print(k)
		if filedName, ok := remap["json"][k]; ok {
			log.Print(opStruct.FieldByName(filedName).Kind(), reflect.TypeOf(v).Kind().String())
			if SameKind(opStruct.FieldByName(filedName).Kind(), reflect.TypeOf(v).Kind()) {
				if opStruct.FieldByName(filedName).CanSet() {
					opStruct.FieldByName(filedName).Set(reflect.ValueOf(v).Convert(opStruct.FieldByName(filedName).Type()))
				}
			}
		}
	}
	return nil
}

/*
	an generaly unmarshal function implement
	accept only pointer to struct or pointer to interface contains struct
*/
func UnmarshalJson2StructGen(t reflect.Type, maps map[string]interface{}) (interface{}, error) {
	if t.Kind() != reflect.Struct {
		return nil, errors.New(ErrorTypeError)
	}
	remap := ReflectTagMap(t)
	_, ok := remap["json"]
	if !ok {
		return nil, errors.New(TagNoExisted)
	}
	log.Print(remap["json"])
	ret := reflect.New(t)
	// log.Print(ret.Kind())
	opStruct := ret.Elem()
	// log.Print(opStruct.Kind())
	for k, v := range maps {
		// log.Print(k)
		if filedName, ok := remap["json"][k]; ok {
			// log.Print(filedName, "  ", opStruct.FieldByName(filedName).Kind(), reflect.TypeOf(v).Kind().String())
			if SameKind(opStruct.FieldByName(filedName).Kind(), reflect.TypeOf(v).Kind()) {
				// log.Print(filedName, " can set ", opStruct.FieldByName(filedName).CanSet(), "can addr ", opStruct.FieldByName(filedName).CanAddr())
				if opStruct.FieldByName(filedName).CanSet() {
					opStruct.FieldByName(filedName).Set(reflect.ValueOf(v).Convert(opStruct.FieldByName(filedName).Type()))
				}
			}
		}
	}
	return ret.Elem().Interface(), nil
}
